/* This file is part of hp2ps, a graph drawer for memory profiles.
   Copyright (C) 2002 The University Court of the University of Glasgow.
   This program is governed by the license contained in the file LICENSE.  */

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "Main.h"
#include "Defines.h"
#include "Error.h"
#include "HpFile.h"
#include "Utilities.h"

#ifndef atof
double atof PROTO((const char *));
#endif

/* own stuff already included */

#define N_MARKS 50		/* start size of the mark table */
#define N_SAMPLES 500		/* start size of the sample table */

char *theident;
char *thestring;
int theinteger;
floatish thefloatish;
int g_ch;					/* last character read  */
token thetok; 					/* last token           */
int linenum;					/* current line number  */
int endfile;					/* true at end of file  */

static boolish gotjob = 0;			/* "JOB" read	        */
static boolish gotdate = 0;			/* "DATE" read          */
static boolish gotvalueunit = 0;		/* "VALUE_UNIT" read    */
static boolish gotsampleunit = 0;		/* "SAMPLE_UNIT" read   */
static boolish insample = 0;			/* true when in sample  */

static floatish lastsample;			/* the last sample time */

static void GetHpLine PROTO((FILE *));		/* forward */
static void GetHpTok  PROTO((FILE *));		/* forward */

static struct entry *GetEntry PROTO((char *));	/* forward */

static void MakeIdentTable PROTO((void));	/* forward */

char *jobstring;
char *datestring;

char *sampleunitstring;
char *valueunitstring;

floatish *samplemap;		/* sample intervals	*/
floatish *markmap;		/* sample marks		*/

/*
 *	An extremely simple parser. The input is organised into lines of 
 *	the form
 *
 *      JOB s              -- job identifier string
 *	DATE s		   -- date string 
 *	SAMPLE_UNIT s	   -- sample unit eg "seconds" 
 *	VALUE_UNIT s	   -- value unit eg "bytes" 
 *	MARK i	   	   -- sample mark 
 *	BEGIN_SAMPLE i 	   -- start of ith sample 
 *	identifier i	   -- there are i identifiers in this sample 
 *	END_SAMPLE i   	   -- end of ith sample 
 *
 */

void
GetHpFile(infp)
  FILE *infp;
{
    nsamples = 0;
    nmarks   = 0;
    nidents  = 0;

    g_ch = ' ';
    endfile = 0;
    linenum = 1;
    lastsample = 0.0;

    GetHpTok(infp);

    while (endfile == 0) {
	GetHpLine(infp);
    }

    if (!gotjob) {
	Error("%s: JOB missing", hpfile);
    }

    if (!gotdate) {
	Error("%s: DATE missing", hpfile);
    }

    if (!gotvalueunit) {
	Error("%s: VALUE_UNIT missing", hpfile);
    }

    if (!gotsampleunit) {
	Error("%s: SAMPLE_UNIT missing", hpfile);
    }

    if (nsamples == 0) {
	Error("%s: contains no samples", hpfile);
    }


    MakeIdentTable();

    fclose(hpfp);
}


/*
 *      Read the next line from the input, check the syntax, and perform
 *	the appropriate action.
 */

static void
GetHpLine(infp)
  FILE* infp;
{
    static intish nmarkmax = 0, nsamplemax = 0;

    switch (thetok) {
    case JOB_TOK:
	GetHpTok(infp);
	if (thetok != STRING_TOK) {
	    Error("%s, line %d: string must follow JOB", hpfile, linenum);
        }
	jobstring = thestring;
	gotjob = 1;
        GetHpTok(infp);
	break;

    case DATE_TOK:
	GetHpTok(infp);
	if (thetok != STRING_TOK) {
	    Error("%s, line %d: string must follow DATE", hpfile, linenum);
        }
	datestring = thestring;
	gotdate = 1;
        GetHpTok(infp);
	break;

    case SAMPLE_UNIT_TOK:
	GetHpTok(infp);
	if (thetok != STRING_TOK) {
	    Error("%s, line %d: string must follow SAMPLE_UNIT", hpfile, 
	          linenum);
        }
	sampleunitstring = thestring;
	gotsampleunit = 1;
        GetHpTok(infp);
	break;

    case VALUE_UNIT_TOK:
        GetHpTok(infp);
	if (thetok != STRING_TOK) {
	    Error("%s, line %d: string must follow VALUE_UNIT", hpfile, 
	          linenum);
        }
	valueunitstring = thestring;
	gotvalueunit = 1;
        GetHpTok(infp);
	break;

    case MARK_TOK:
	GetHpTok(infp);
        if (thetok != FLOAT_TOK) {
            Error("%s, line %d, floating point number must follow MARK",
	          hpfile, linenum);
        }
	if (insample) {
	    Error("%s, line %d, MARK occurs within sample", hpfile, linenum);
	}
	if (nmarks >= nmarkmax) {
	    if (!markmap) {
		nmarkmax = N_MARKS;
		markmap = (floatish*) xmalloc(nmarkmax * sizeof(floatish));
	    } else {
		nmarkmax *= 2;
		markmap = (floatish*) xrealloc(markmap, nmarkmax * sizeof(floatish));
	    }
	}
	markmap[ nmarks++ ] = thefloatish; 
        GetHpTok(infp);
        break;

    case BEGIN_SAMPLE_TOK: 
	insample = 1;
	GetHpTok(infp); 
	if (thetok != FLOAT_TOK) {
	    Error("%s, line %d, floating point number must follow BEGIN_SAMPLE",	          hpfile, linenum);
	}
	if (thefloatish < lastsample) {
	    Error("%s, line %d, samples out of sequence", hpfile, linenum);
	} else {
	    lastsample = thefloatish;
        }
	if (nsamples >= nsamplemax) {
	    if (!samplemap) {
		nsamplemax = N_SAMPLES;
		samplemap = (floatish*) xmalloc(nsamplemax * sizeof(floatish));
	    } else {
		nsamplemax *= 2;
		samplemap = (floatish*) xrealloc(samplemap, 
	                                      nsamplemax * sizeof(floatish));
	    }
	}
	samplemap[ nsamples ] = thefloatish;
	GetHpTok(infp);
	break;

    case END_SAMPLE_TOK: 
	insample = 0;
	GetHpTok(infp); 
	if (thetok != FLOAT_TOK) {
	    Error("%s, line %d: floating point number must follow END_SAMPLE", 
                  hpfile, linenum);
	}
        nsamples++;
	GetHpTok(infp);
	break;

    case IDENTIFIER_TOK:
	GetHpTok(infp);
	if (thetok != INTEGER_TOK) {
	    Error("%s, line %d: integer must follow identifier", hpfile, 
                  linenum);
	}
        StoreSample(GetEntry(theident), nsamples, (floatish) theinteger);
	GetHpTok(infp); 
        break;

    case EOF_TOK:
        endfile = 1;
	break;

    default:
	Error("%s, line %d: %s unexpected", hpfile, linenum,
	      TokenToString(thetok));
	break;
    }
}


char *
TokenToString(t)
  token t;
{
   switch (t) {
	case EOF_TOK:		return "EOF";
	case INTEGER_TOK:	return "integer";
	case FLOAT_TOK:		return "floating point number";
	case IDENTIFIER_TOK:	return "identifier";
	case STRING_TOK:	return "string";
	case BEGIN_SAMPLE_TOK:  return "BEGIN_SAMPLE";
	case END_SAMPLE_TOK:    return "END_SAMPLE";
	case JOB_TOK:		return "JOB";
	case DATE_TOK:		return "DATE";
	case SAMPLE_UNIT_TOK:   return "SAMPLE_UNIT";
	case VALUE_UNIT_TOK:    return "VALUE_UNIT";
	case MARK_TOK:		return "MARK";

	case X_RANGE_TOK:	return "X_RANGE";
	case Y_RANGE_TOK:	return "Y_RANGE";
	case ORDER_TOK:		return "ORDER";
	case SHADE_TOK:		return "SHADE";
        default:		return "(strange token)";
    }
}

/*
 *	Read the next token from the input and assign its value
 *	to the global variable "thetok". In the case of numbers,
 *	the corresponding value is also assigned to "theinteger"
 *	or "thefloatish" as appropriate; in the case of identifiers 
 *	it is assigned to "theident".
 */

static void
GetHpTok(infp)
  FILE* infp;
{

    while (isspace(g_ch)) {		/* skip whitespace */
	if (g_ch == '\n') linenum++;
	g_ch = getc(infp);
    } 

    if (g_ch == EOF) {
	thetok = EOF_TOK;
	return;
    }

    if (isdigit(g_ch)) {
	thetok = GetNumber(infp);
	return;
    } else if (g_ch == '\"') {
	GetString(infp);
	thetok = STRING_TOK;
	return;
    } else if (IsIdChar(g_ch)) {
	ASSERT(! (isdigit(g_ch)));	/* g_ch can't be a digit here */
	GetIdent(infp);
	if (!isupper(theident[0])) {
	    thetok = IDENTIFIER_TOK;
	} else if (strcmp(theident, "BEGIN_SAMPLE") == 0) {
            thetok = BEGIN_SAMPLE_TOK;
	} else if (strcmp(theident, "END_SAMPLE") == 0) {
            thetok = END_SAMPLE_TOK;
	} else if (strcmp(theident, "JOB") == 0) {
	    thetok = JOB_TOK;
	} else if (strcmp(theident, "DATE") == 0) {
	    thetok = DATE_TOK;
	} else if (strcmp(theident, "SAMPLE_UNIT") == 0) {
	    thetok = SAMPLE_UNIT_TOK;
	} else if (strcmp(theident, "VALUE_UNIT") == 0) {
	    thetok = VALUE_UNIT_TOK;
	} else if (strcmp(theident, "MARK") == 0) {
	    thetok = MARK_TOK;
	} else {
            thetok = IDENTIFIER_TOK;
	}
	return;
    } else {
	Error("%s, line %d: strange character (%c)", hpfile, linenum, g_ch);
    }
}


/*
 *	Read a sequence of digits and convert the result to an integer
 *	or floating point value (assigned to the "theinteger" or 
 *	"thefloatish").
 */

static char numberstring[ NUMBER_LENGTH ];

token
GetNumber(infp)
  FILE* infp;
{
    int i;
    int containsdot;
 
    ASSERT(isdigit(ch)); /* we must have a digit to start with */

    containsdot = 0;

    for (i = 0; i < NUMBER_LENGTH && (isdigit(g_ch) || g_ch == '.'); i++) {
        numberstring[ i ] = g_ch;
        containsdot |= (g_ch == '.'); 
        g_ch = getc(infp);
    }   
 
    ASSERT(i < NUMBER_LENGTH); /* did not overflow */

    numberstring[ i ] = '\0';
 
    if (containsdot) {
        thefloatish = (floatish) atof(numberstring);
	return FLOAT_TOK;
    } else {
	theinteger = atoi(numberstring);
	return INTEGER_TOK;
    }
}

/*
 *	Read a sequence of identifier characters and assign the result 
 *	to the string "theident".
 */

void
GetIdent(infp)
  FILE *infp;
{
    unsigned int i;
    char idbuffer[5000];

    for (i = 0; i < (sizeof idbuffer)-1 && IsIdChar(g_ch); i++) {
	idbuffer[ i ] = g_ch;
	g_ch = getc(infp);
    }
    
    idbuffer[ i ] = '\0';

    if (theident)
	free(theident);

    theident = copystring(idbuffer);
}


/*
 *	Read a sequence of characters that make up a string and 
 *	assign the result to "thestring".
 */

void
GetString(infp)
  FILE *infp;
{
    unsigned int i;
    char stringbuffer[5000];

    ASSERT(ch == '\"');

    g_ch = getc(infp);	/* skip the '\"' that begins the string */

    for (i = 0; i < (sizeof stringbuffer)-1 && g_ch != '\"'; i++) {
	stringbuffer[ i ] = g_ch;
	g_ch = getc(infp);
    }

    stringbuffer[i] = '\0'; 
    thestring = copystring(stringbuffer);

    ASSERT(g_ch == '\"');

    g_ch = getc(infp);      /* skip the '\"' that terminates the string */
}

boolish
IsIdChar(ch)
  int ch;
{
    return (!isspace(ch));
}


/*
 *      The information associated with each identifier is stored
 *	in a linked list of chunks. The table below allows the list
 *	of chunks to be retrieved given an identifier name.
 */

#define N_HASH       	513 

static struct entry* hashtable[ N_HASH ];

static intish
Hash(char* s)
{
    int r;
 
    for (r = 0; *s; s++) {
        r = r + r + r + *s;
    }

    if (r < 0) r = -r;

    return r % N_HASH;
}

/*
 *      Get space for a new chunk. Initialise it, and return a pointer 
 *	to the new chunk.
 */
 
static struct chunk*
MakeChunk(void)
{
    struct chunk* ch;
    struct datapoint* d;

    ch = (struct chunk*) xmalloc( sizeof(struct chunk) );
 
    d = (struct datapoint*) xmalloc (sizeof(struct datapoint) * N_CHUNK);

    ch->nd = 0; 
    ch->d = d;
    ch->next = 0;
    return ch;
}


/*
 *      Get space for a new entry. Initialise it, and return a pointer 
 *	to the new entry.
 */
 
struct entry *
MakeEntry(name)
  char *name;
{
    struct entry* e;

    e = (struct entry *) xmalloc(sizeof(struct entry));
    e->chk = MakeChunk();
    e->name = copystring(name); 
    return e;
}

/*
 *	Get the entry associated with "name", creating a new entry if 
 *	necessary.
 */

static struct entry *
GetEntry(name)
  char* name;
{
    intish h;
    struct entry* e;
 
    h = Hash(name);
 
    for (e = hashtable[ h ]; e; e = e->next) {
        if (strcmp(e->name, name) == 0) {
            break;
        }
    }
 
    if (e) {
	return (e); 
    } else {
        nidents++;
        e = MakeEntry(name);
        e->next = hashtable[ h ];
        hashtable[ h ] = e;
        return (e);
    }
}


/*
 *      Store information from a sample. 
 */
 
void
StoreSample(en, bucket, value)
  struct entry* en; intish bucket; floatish value;
{
    struct chunk* chk; 

    for (chk = en->chk; chk->next != 0; chk = chk->next)
	; 

    if (chk->nd < N_CHUNK) {
	chk->d[ chk->nd ].bucket = bucket;
	chk->d[ chk->nd ].value  = value;
	chk->nd += 1;
    } else {
	struct chunk* t;
	t = chk->next = MakeChunk(); 
	t->d[ 0 ].bucket = bucket;
	t->d[ 0 ].value  = value;
	t->nd += 1;
    }
}


struct entry** identtable;

/*
 *	The hash table is useful while reading the input, but it
 *	becomes a liability thereafter. The code below converts 
 *	it to a more easily processed table.
 */

static void
MakeIdentTable()
{
    intish i;
    intish j;
    struct entry* e;

    nidents = 0;
    for (i = 0; i < N_HASH; i++) {
        for (e = hashtable[ i ]; e; e = e->next) {
	    nidents++;
        }
    }

    identtable = (struct entry**) xmalloc(nidents * sizeof(struct entry*));
    j = 0;

    for (i = 0; i < N_HASH; i++) {
        for (e = hashtable[ i ]; e; e = e->next, j++) {
	    identtable[ j ] = e; 
        }
    }
}
